MongoDB security fixes
user
We have a problem with our mongodb server setup in AWS EC2. Earlier around 2AM we've experienced sudden downtime and traced back due to MongoDB server shutting down. We have several speculation like a ddos attack, a possible intrusion or disk space issue. Our MongoDB server is hosted on a separate ec2 instance and other instances just connects to it. Our initial security is just the security group of the AWS ec2 but I think this is not enough. We also have no administrative password in place. our MongoDB version right now is 3.4.24. Can you enumerate any possible quick solutions that can be run as a script or can be automated?
ChatGPT
Got it. You’re in a dangerous spot right now:
- **MongoDB 3.4.24** is **end-of-life (EOL)** and has known security vulnerabilities.
- Running **without authentication** and **open to the internet** (even if only via AWS security group) is extremely risky.
- The sudden shutdown at 2AM could be due to **disk space exhaustion**, **OOM (out of memory)**, or an **intrusion** (attackers often scan and exploit unauthenticated MongoDB).
Here are **quick actionable steps** you can script/automate to stabilize and secure your server:
---
## 🔒 Immediate Security Fixes
1. **Restrict Network Access (AWS Security Group + iptables fail-safe)**
Only allow connections from your application servers. Run on MongoDB server:
```bash
# Example: only allow internal app server 10.0.1.15
sudo iptables -A INPUT -p tcp -s 10.0.1.15 --dport 27017 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 27017 -j DROP
sudo netfilter-persistent save
```
_(This prevents internet-wide scans hitting your DB.)_
2. **Enable MongoDB Authentication**
Edit `/etc/mongod.conf` and enable:
```yaml
security:
authorization: enabled
net:
bindIp: 127.0.0.1,10.0.1.15 # Replace with internal app server IPs
```
Restart MongoDB:
```bash
sudo systemctl restart mongod
```
3. **Create Admin User (root)**
Start Mongo **without auth** just once:
```bash
mongo
```
Then inside shell:
```javascript
use admin
db.createUser({
user: "admin",
pwd: "StrongPassword123!",
roles: [ { role: "root", db: "admin" } ]
})
```
Exit, restart MongoDB with auth enabled.
4. **Fail2ban or UFW basic defense**
Install Fail2ban to prevent brute force:
```bash
sudo apt-get install -y fail2ban
sudo systemctl enable fail2ban
```
---
## 📊 Monitoring & Prevention Scripts
1. **Disk space monitoring (auto-clean logs)**
Create `/usr/local/bin/check_mongo.sh`:
```bash
#!/bin/bash
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "Disk usage critical: $USAGE%, cleaning logs..."
journalctl --vacuum-size=200M
find /var/log/mongodb/ -type f -name "*.log" -size +50M -delete
fi
```
Make executable & schedule in cron:
```bash
chmod +x /usr/local/bin/check_mongo.sh
echo "*/15 * * * * root /usr/local/bin/check_mongo.sh" | sudo tee -a /etc/crontab
```
2. **MongoDB health watchdog**
Create `/usr/local/bin/mongo_watchdog.sh`:
```bash
#!/bin/bash
if ! pgrep mongod > /dev/null; then
echo "MongoDB down, restarting..." | mail -s "MongoDB Alert" admin@example.com
systemctl restart mongod
fi
```
Add to cron:
```bash
*/5 * * * * root /usr/local/bin/mongo_watchdog.sh
```
3. **Auto-rotate Mongo logs**
Add `/etc/logrotate.d/mongodb`:
```
/var/log/mongodb/mongod.log {
weekly
rotate 4
compress
delaycompress
missingok
notifempty
create 640 mongodb mongodb
sharedscripts
postrotate
systemctl reload mongod > /dev/null
endscript
}
```
---
## 🚀 Medium-Term Recommendations
- **Upgrade MongoDB ASAP** (3.4 is too old). Even 4.4 is near EOL, latest stable is 7.0.
- **Enable TLS/SSL** for client-server connections (self-signed or Let’s Encrypt).
- **Enable CloudWatch or Prometheus metrics** for monitoring uptime, memory, disk.
- **Consider migrating to MongoDB Atlas** if management overhead is too high.
---
✅ If you want, I can prepare you a **single hardening script** that:
- Updates iptables rules,
- Enables MongoDB authentication,
- Creates an admin user,
- Sets up watchdog + disk monitoring + log rotation automatically.
Do you want me to generate that **all-in-one setup script** for Ubuntu (assuming your MongoDB EC2 is Ubuntu)?
user
Yes please. And can you include this in your context when thinking about the solution, this is our current script when starting the database server,
mongod --fork --smallfiles --storageEngine=mmapv1 --port 27017 --dbpath ~/mongodb-data --logpath ~/mongodb-logs/mongodb.log
Can you also improve this and include this in your script?
ChatGPT
Perfect, thanks for sharing your current startup command — that helps a lot.
Right now, your command has several risks:
- **No authentication** (anyone connecting can read/write/erase).
- **mmapv1 storage engine** (deprecated; WiredTiger is default since MongoDB 3.2 and far better).
- **No systemd service** (you’re manually forking, so no watchdog/restart on crash).
- **No ulimit tuning** (could run out of file handles).
I’ll give you an **all-in-one Ubuntu hardening script** that:
1. Installs MongoDB 3.4 (from existing repos).
2. Creates secure directories for data/logs.
3. Sets up **systemd service** for auto-start & crash recovery.
4. Switches to **WiredTiger** (safer than mmapv1).
5. Enables **authentication**.
6. Creates an **admin user**.
7. Adds **iptables restrictions**.
8. Configures **log rotation** + **disk watchdog** + **Mongo watchdog**.
---
# 🛡 Hardened MongoDB Setup Script (Ubuntu EC2)
Save as `setup_secure_mongo.sh` and run with `sudo bash setup_secure_mongo.sh`:
```bash
#!/bin/bash
set -e
# ================================
# CONFIG (edit as needed)
# ================================
DB_PATH="/var/lib/mongodb"
LOG_PATH="/var/log/mongodb/mongod.log"
ADMIN_USER="admin"
ADMIN_PASS="StrongPassword123!"
APP_SERVER_IP="10.0.1.15" # replace with your app server's internal IP
# ================================
# 1. Install MongoDB 3.4 (if not installed)
# ================================
if ! command -v mongod &> /dev/null; then
echo "[*] Installing MongoDB 3.4..."
apt-get update
apt-get install -y mongodb-org=3.4.24 mongodb-org-server=3.4.24 mongodb-org-shell=3.4.24 mongodb-org-mongos=3.4.24 mongodb-org-tools=3.4.24
fi
# ================================
# 2. Create paths & permissions
# ================================
mkdir -p $DB_PATH
mkdir -p $(dirname $LOG_PATH)
chown -R mongodb:mongodb $DB_PATH $(dirname $LOG_PATH)
# ================================
# 3. Systemd service
# ================================
cat > /etc/systemd/system/mongod.service <<EOF
[Unit]
Description=MongoDB Database Server
After=network.target
[Service]
User=mongodb
Group=mongodb
ExecStart=/usr/bin/mongod --config /etc/mongod.conf
PIDFile=/var/run/mongodb/mongod.pid
LimitNOFILE=64000
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# ================================
# 4. MongoDB config file
# ================================
cat > /etc/mongod.conf <<EOF
storage:
dbPath: $DB_PATH
journal:
enabled: true
engine: wiredTiger
systemLog:
destination: file
path: $LOG_PATH
logAppend: true
net:
port: 27017
bindIp: 127.0.0.1,$APP_SERVER_IP
security:
authorization: enabled
EOF
# ================================
# 5. Firewall rules (iptables)
# ================================
echo "[*] Configuring iptables firewall..."
iptables -A INPUT -p tcp -s $APP_SERVER_IP --dport 27017 -j ACCEPT
iptables -A INPUT -p tcp --dport 27017 -j DROP
netfilter-persistent save || true
# ================================
# 6. Start MongoDB
# ================================
systemctl daemon-reexec
systemctl enable mongod
systemctl restart mongod
# ================================
# 7. Create Admin User
# ================================
echo "[*] Creating MongoDB admin user..."
mongo admin --eval "db.createUser({ user: '$ADMIN_USER', pwd: '$ADMIN_PASS', roles:[{ role:'root', db:'admin'}] })" || true
# ================================
# 8. Setup log rotation
# ================================
cat > /etc/logrotate.d/mongodb <<EOF
$LOG_PATH {
weekly
rotate 4
compress
delaycompress
missingok
notifempty
create 640 mongodb mongodb
sharedscripts
postrotate
systemctl reload mongod > /dev/null
endscript
}
EOF
# ================================
# 9. Disk watchdog
# ================================
cat > /usr/local/bin/check_mongo_disk.sh <<'EOF'
#!/bin/bash
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "Disk usage critical: $USAGE%, cleaning logs..."
journalctl --vacuum-size=200M
find /var/log/mongodb/ -type f -name "*.log" -size +50M -delete
fi
EOF
chmod +x /usr/local/bin/check_mongo_disk.sh
echo "*/15 * * * * root /usr/local/bin/check_mongo_disk.sh" >> /etc/crontab
# ================================
# 10. Mongo watchdog
# ================================
cat > /usr/local/bin/mongo_watchdog.sh <<'EOF'
#!/bin/bash
if ! pgrep mongod > /dev/null; then
echo "MongoDB down, restarting..." | mail -s "MongoDB Alert" admin@example.com
systemctl restart mongod
fi
EOF
chmod +x /usr/local/bin/mongo_watchdog.sh
echo "*/5 * * * * root /usr/local/bin/mongo_watchdog.sh" >> /etc/crontab
echo "[✔] MongoDB hardened setup complete!"
echo "Connect with: mongo -u $ADMIN_USER -p $ADMIN_PASS --authenticationDatabase admin"
```
---
## 🚀 Improvements over your current command
- Runs via **systemd** instead of `--fork` (auto-restart, logs in `journalctl`).
- Uses **WiredTiger** instead of `mmapv1` (better performance & safety).
- **Authorization enabled** (you’ll need the admin password to connect).
- Restricts access to **localhost + app server IP only**.
- Adds **log rotation, disk cleanup, watchdogs** for resilience.
---
👉 Do you want me to also include **TLS/SSL with self-signed certs or Let’s Encrypt** in this script, so your MongoDB connections are encrypted instead of plain-text?
user
Will this cause problem with our existing data in the database when migrating to wiredtiger?
ChatGPT
That’s a really important question 👍 — thanks for catching it before you run the script.
Here’s the situation with **migrating from mmapv1 → WiredTiger**:
---
### 🔎 What happens if you just change the config to WiredTiger?
- MongoDB **does not automatically convert** mmapv1 data files into WiredTiger format.
- If you simply change `storage.engine: wiredTiger` in `mongod.conf` and restart, **MongoDB will fail to start** because the existing data files are in the wrong format.
- You’ll see something like:
```
NonWiredTiger data files detected...
```
---
### ✅ Correct migration process
To safely move from `mmapv1` to `wiredTiger`, you need to **dump and restore** your data:
1. **Dump your data**:
```bash
mongodump --out ~/mongo-backup-$(date +%F)
```
2. **Stop MongoDB**:
```bash
sudo systemctl stop mongod
```
3. **Move old mmapv1 data aside**:
```bash
sudo mv /var/lib/mongodb /var/lib/mongodb-mmapv1-backup
sudo mkdir -p /var/lib/mongodb
sudo chown -R mongodb:mongodb /var/lib/mongodb
```
4. **Enable WiredTiger in config** (`/etc/mongod.conf`):
```yaml
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true
engine: wiredTiger
```
5. **Restart MongoDB** (it will create fresh WiredTiger storage files):
```bash
sudo systemctl start mongod
```
6. **Restore the data**:
```bash
mongorestore ~/mongo-backup-$(date +%F)
```
---
### ⚠️ If you **must** stay on mmapv1 for now
- You can keep `--storageEngine=mmapv1` in the service config and **still enable authentication, firewall, log rotation, watchdogs**.
- That means you get the **security benefits** without risking data loss/migration downtime right away.
- Later, you can schedule a proper **dump → restore migration** to WiredTiger (or better, upgrade MongoDB to 4.x+ where mmapv1 is completely removed).
---
👉 Question for you:
Do you want me to **adjust the script** so it keeps using `mmapv1` for now (safe with your existing data), or should I prep it for a **full dump & restore migration to WiredTiger**?
user
Can you include this in the script and also include in the script a weekly backup and backup rotation to prevent disk space issues
ChatGPT
Got it ✅ — I’ll extend the script so it:
1. **Keeps your current data engine (`mmapv1`)** to avoid immediate migration problems.
2. **Adds an optional migration path** (`mongodump → WiredTiger restore`) that you can trigger when ready.
3. **Sets up automatic weekly backups** using `mongodump`.
4. **Adds backup rotation** (old backups cleaned up to prevent disk filling up).
---
# 🛡 Secure MongoDB Setup + Backup Script
Save as `setup_secure_mongo.sh` and run with `sudo bash setup_secure_mongo.sh`:
```bash
#!/bin/bash
set -e
# ================================
# CONFIG (edit as needed)
# ================================
DB_PATH="/var/lib/mongodb"
LOG_PATH="/var/log/mongodb/mongod.log"
BACKUP_PATH="/var/backups/mongodb"
ADMIN_USER="admin"
ADMIN_PASS="StrongPassword123!"
APP_SERVER_IP="10.0.1.15" # replace with your app server's internal IP
BACKUP_RETENTION_DAYS=30 # auto-delete backups older than this
# ================================
# 1. Install MongoDB 3.4 (if not installed)
# ================================
if ! command -v mongod &> /dev/null; then
echo "[*] Installing MongoDB 3.4..."
apt-get update
apt-get install -y mongodb-org=3.4.24 mongodb-org-server=3.4.24 mongodb-org-shell=3.4.24 mongodb-org-mongos=3.4.24 mongodb-org-tools=3.4.24
fi
# ================================
# 2. Create paths & permissions
# ================================
mkdir -p $DB_PATH
mkdir -p $(dirname $LOG_PATH)
mkdir -p $BACKUP_PATH
chown -R mongodb:mongodb $DB_PATH $(dirname $LOG_PATH) $BACKUP_PATH
# ================================
# 3. Systemd service
# ================================
cat > /etc/systemd/system/mongod.service <<EOF
[Unit]
Description=MongoDB Database Server
After=network.target
[Service]
User=mongodb
Group=mongodb
ExecStart=/usr/bin/mongod --config /etc/mongod.conf
PIDFile=/var/run/mongodb/mongod.pid
LimitNOFILE=64000
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# ================================
# 4. MongoDB config file (keep mmapv1 for existing data)
# ================================
cat > /etc/mongod.conf <<EOF
storage:
dbPath: $DB_PATH
journal:
enabled: true
engine: mmapv1 # keep mmapv1 to avoid data migration issues
systemLog:
destination: file
path: $LOG_PATH
logAppend: true
net:
port: 27017
bindIp: 127.0.0.1,$APP_SERVER_IP
security:
authorization: enabled
EOF
# ================================
# 5. Firewall rules (iptables)
# ================================
echo "[*] Configuring iptables firewall..."
iptables -A INPUT -p tcp -s $APP_SERVER_IP --dport 27017 -j ACCEPT
iptables -A INPUT -p tcp --dport 27017 -j DROP
netfilter-persistent save || true
# ================================
# 6. Start MongoDB
# ================================
systemctl daemon-reexec
systemctl enable mongod
systemctl restart mongod
# ================================
# 7. Create Admin User
# ================================
echo "[*] Creating MongoDB admin user..."
mongo admin --eval "db.createUser({ user: '$ADMIN_USER', pwd: '$ADMIN_PASS', roles:[{ role:'root', db:'admin'}] })" || true
# ================================
# 8. Setup log rotation
# ================================
cat > /etc/logrotate.d/mongodb <<EOF
$LOG_PATH {
weekly
rotate 4
compress
delaycompress
missingok
notifempty
create 640 mongodb mongodb
sharedscripts
postrotate
systemctl reload mongod > /dev/null
endscript
}
EOF
# ================================
# 9. Disk watchdog
# ================================
cat > /usr/local/bin/check_mongo_disk.sh <<'EOF'
#!/bin/bash
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "Disk usage critical: $USAGE%, cleaning logs..."
journalctl --vacuum-size=200M
find /var/log/mongodb/ -type f -name "*.log" -size +50M -delete
find /var/backups/mongodb/ -type d -mtime +30 -exec rm -rf {} \; 2>/dev/null
fi
EOF
chmod +x /usr/local/bin/check_mongo_disk.sh
echo "*/15 * * * * root /usr/local/bin/check_mongo_disk.sh" >> /etc/crontab
# ================================
# 10. Mongo watchdog
# ================================
cat > /usr/local/bin/mongo_watchdog.sh <<'EOF'
#!/bin/bash
if ! pgrep mongod > /dev/null; then
echo "MongoDB down, restarting..." | mail -s "MongoDB Alert" admin@example.com
systemctl restart mongod
fi
EOF
chmod +x /usr/local/bin/mongo_watchdog.sh
echo "*/5 * * * * root /usr/local/bin/mongo_watchdog.sh" >> /etc/crontab
# ================================
# 11. Backup script (weekly + rotation)
# ================================
cat > /usr/local/bin/mongo_backup.sh <<EOF
#!/bin/bash
BACKUP_DIR="$BACKUP_PATH/mongo-\$(date +%F)"
mongodump --out \$BACKUP_DIR --username $ADMIN_USER --password $ADMIN_PASS --authenticationDatabase admin
# Delete backups older than $BACKUP_RETENTION_DAYS days
find $BACKUP_PATH -type d -mtime +$BACKUP_RETENTION_DAYS -exec rm -rf {} \;
EOF
chmod +x /usr/local/bin/mongo_backup.sh
# Weekly cronjob (every Sunday at 2AM)
echo "0 2 * * 0 root /usr/local/bin/mongo_backup.sh" >> /etc/crontab
# ================================
# Info for operator
# ================================
echo "[✔] MongoDB hardened setup complete!"
echo "Connect with: mongo -u $ADMIN_USER -p $ADMIN_PASS --authenticationDatabase admin"
echo "Backups stored in: $BACKUP_PATH (rotated every $BACKUP_RETENTION_DAYS days)"
echo "Currently using mmapv1 for compatibility. When ready, migrate with mongodump -> WiredTiger restore."
```
---
### 🚀 What’s new
- **Still uses mmapv1** → no data migration risk now.
- **Weekly backups** (`mongodump` → `/var/backups/mongodb/YYYY-MM-DD`).
- **Backup rotation** → deletes backups older than 30 days (configurable).
- **Disk watchdog** also cleans old backups when disk is near full.
- Migration to **WiredTiger** is documented and can be run later safely.
---
👉 Do you want me to also prepare a **separate migration script** (that automates dump → switch to WiredTiger → restore) so you can run it in a scheduled maintenance window later?
user
Please include in the script to automatically migrate to wiredtiger if it is not yet in that engine
ChatGPT
Nice 👍 — let’s bake **automatic migration** into the script.
Here’s how it will behave:
- If MongoDB is already **using WiredTiger**, nothing changes.
- If MongoDB is still on **mmapv1**, the script will:
1. Run a **full `mongodump` backup**.
2. Stop MongoDB.
3. Move the old **mmapv1 data directory** aside as a backup.
4. Change the config to **WiredTiger**.
5. Start MongoDB fresh (empty WiredTiger storage).
6. **Restore the dump** into WiredTiger.
7. Confirm success, and keep the old mmapv1 data folder as a fallback.
This way you get **automatic safe migration**, and you won’t have to manually dump/restore later.
---
# 🛡 Secure MongoDB Setup + Auto-Migrate to WiredTiger + Backup
Save as `setup_secure_mongo.sh` and run with `sudo bash setup_secure_mongo.sh`:
```bash
#!/bin/bash
set -e
# ================================
# CONFIG (edit as needed)
# ================================
DB_PATH="/var/lib/mongodb"
LOG_PATH="/var/log/mongodb/mongod.log"
BACKUP_PATH="/var/backups/mongodb"
ADMIN_USER="admin"
ADMIN_PASS="StrongPassword123!"
APP_SERVER_IP="10.0.1.15" # replace with your app server's internal IP
BACKUP_RETENTION_DAYS=30 # auto-delete backups older than this
# ================================
# 1. Install MongoDB 3.4 (if not installed)
# ================================
if ! command -v mongod &> /dev/null; then
echo "[*] Installing MongoDB 3.4..."
apt-get update
apt-get install -y mongodb-org=3.4.24 mongodb-org-server=3.4.24 mongodb-org-shell=3.4.24 mongodb-org-mongos=3.4.24 mongodb-org-tools=3.4.24
fi
# ================================
# 2. Create paths & permissions
# ================================
mkdir -p $DB_PATH
mkdir -p $(dirname $LOG_PATH)
mkdir -p $BACKUP_PATH
chown -R mongodb:mongodb $DB_PATH $(dirname $LOG_PATH) $BACKUP_PATH
# ================================
# 3. Systemd service
# ================================
cat > /etc/systemd/system/mongod.service <<EOF
[Unit]
Description=MongoDB Database Server
After=network.target
[Service]
User=mongodb
Group=mongodb
ExecStart=/usr/bin/mongod --config /etc/mongod.conf
PIDFile=/var/run/mongodb/mongod.pid
LimitNOFILE=64000
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# ================================
# 4. Detect storage engine
# ================================
ENGINE=$(mongo --quiet --eval "db.serverStatus().storageEngine.name" 2>/dev/null || echo "unknown")
echo "[*] Detected storage engine: $ENGINE"
# ================================
# 5. Configure MongoDB (default to WiredTiger)
# ================================
cat > /etc/mongod.conf <<EOF
storage:
dbPath: $DB_PATH
journal:
enabled: true
engine: wiredTiger
systemLog:
destination: file
path: $LOG_PATH
logAppend: true
net:
port: 27017
bindIp: 127.0.0.1,$APP_SERVER_IP
security:
authorization: enabled
EOF
# ================================
# 6. If mmapv1, migrate to WiredTiger
# ================================
if [[ "$ENGINE" == "mmapv1" ]]; then
echo "[*] Migrating data from mmapv1 to WiredTiger..."
# Step 1: Backup via mongodump
BACKUP_DIR="$BACKUP_PATH/migration-\$(date +%F-%H%M)"
mkdir -p \$BACKUP_DIR
mongodump --out \$BACKUP_DIR
echo "[*] Backup created at \$BACKUP_DIR"
# Step 2: Stop MongoDB
systemctl stop mongod
# Step 3: Move old mmapv1 data directory aside
mv $DB_PATH ${DB_PATH}-mmapv1-backup-\$(date +%F-%H%M)
mkdir -p $DB_PATH
chown -R mongodb:mongodb $DB_PATH
# Step 4: Restart MongoDB fresh with WiredTiger
systemctl start mongod
sleep 5
# Step 5: Restore backup into WiredTiger
mongorestore \$BACKUP_DIR
echo "[✔] Migration complete. Old mmapv1 data preserved at ${DB_PATH}-mmapv1-backup-*"
fi
# ================================
# 7. Firewall rules (iptables)
# ================================
echo "[*] Configuring iptables firewall..."
iptables -A INPUT -p tcp -s $APP_SERVER_IP --dport 27017 -j ACCEPT
iptables -A INPUT -p tcp --dport 27017 -j DROP
netfilter-persistent save || true
# ================================
# 8. Start MongoDB
# ================================
systemctl daemon-reexec
systemctl enable mongod
systemctl restart mongod
# ================================
# 9. Create Admin User
# ================================
echo "[*] Creating MongoDB admin user..."
mongo admin --eval "db.createUser({ user: '$ADMIN_USER', pwd: '$ADMIN_PASS', roles:[{ role:'root', db:'admin'}] })" || true
# ================================
# 10. Setup log rotation
# ================================
cat > /etc/logrotate.d/mongodb <<EOF
$LOG_PATH {
weekly
rotate 4
compress
delaycompress
missingok
notifempty
create 640 mongodb mongodb
sharedscripts
postrotate
systemctl reload mongod > /dev/null
endscript
}
EOF
# ================================
# 11. Disk watchdog
# ================================
cat > /usr/local/bin/check_mongo_disk.sh <<'EOF'
#!/bin/bash
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "Disk usage critical: $USAGE%, cleaning logs..."
journalctl --vacuum-size=200M
find /var/log/mongodb/ -type f -name "*.log" -size +50M -delete
find /var/backups/mongodb/ -type d -mtime +30 -exec rm -rf {} \; 2>/dev/null
fi
EOF
chmod +x /usr/local/bin/check_mongo_disk.sh
echo "*/15 * * * * root /usr/local/bin/check_mongo_disk.sh" >> /etc/crontab
# ================================
# 12. Mongo watchdog
# ================================
cat > /usr/local/bin/mongo_watchdog.sh <<'EOF'
#!/bin/bash
if ! pgrep mongod > /dev/null; then
echo "MongoDB down, restarting..." | mail -s "MongoDB Alert" admin@example.com
systemctl restart mongod
fi
EOF
chmod +x /usr/local/bin/mongo_watchdog.sh
echo "*/5 * * * * root /usr/local/bin/mongo_watchdog.sh" >> /etc/crontab
# ================================
# 13. Backup script (weekly + rotation)
# ================================
cat > /usr/local/bin/mongo_backup.sh <<EOF
#!/bin/bash
BACKUP_DIR="$BACKUP_PATH/mongo-\$(date +%F)"
mongodump --out \$BACKUP_DIR --username $ADMIN_USER --password $ADMIN_PASS --authenticationDatabase admin
# Delete backups older than $BACKUP_RETENTION_DAYS days
find $BACKUP_PATH -type d -mtime +$BACKUP_RETENTION_DAYS -exec rm -rf {} \;
EOF
chmod +x /usr/local/bin/mongo_backup.sh
# Weekly cronjob (every Sunday at 2AM)
echo "0 2 * * 0 root /usr/local/bin/mongo_backup.sh" >> /etc/crontab
# ================================
# Info for operator
# ================================
echo "[✔] MongoDB hardened setup complete!"
echo "Admin login: mongo -u $ADMIN_USER -p $ADMIN_PASS --authenticationDatabase admin"
echo "Backups stored in: $BACKUP_PATH (rotated every $BACKUP_RETENTION_DAYS days)"
echo "If migration ran, old mmapv1 data is preserved in ${DB_PATH}-mmapv1-backup-*"
```
---
### 🚀 What this does
- Checks current storage engine.
- If **mmapv1** → does safe `mongodump → stop → reconfigure WiredTiger → restore`.
- If already **WiredTiger** → continues setup as-is.
- Weekly backups + rotation.
- Disk/log watchdogs.
- Firewall & authentication enforced.
---
⚠️ Migration will briefly **take MongoDB offline** during the dump/restore process (downtime depends on DB size).
👉 Do you want me to also add an **email or Slack alert** after migration & backups, so you’re notified of success/failure?
user
Can you modify the script such that the username and password variables should be renamed to PUBLIC PHRASE and PRIVATE PHRASE respectively? Also we want that these values should be stored in a file. Also any sensitive input values or dynamic values in the script should be loaded from a separate file so that the script does not contain sensitive information
ChatGPT
Perfect 👌 — great call. Storing sensitive values (like credentials, IPs, retention periods) in a **separate environment file** makes the script reusable, auditable, and safer.
Here’s what I’ll do:
1. **Rename variables**:
- `ADMIN_USER` → `PUBLIC_PHRASE`
- `ADMIN_PASS` → `PRIVATE_PHRASE`
2. **Move sensitive/dynamic values into a config file**:
- `PUBLIC_PHRASE` (admin username)
- `PRIVATE_PHRASE` (admin password)
- `APP_SERVER_IP` (allowed app IP)
- `BACKUP_RETENTION_DAYS`
→ All will be stored in `/etc/mongo-secure.conf`.
3. **Script loads config file** before execution, so you can edit values anytime without touching the script.
---
# 🔒 Example Config File
Create `/etc/mongo-secure.conf` (only root-readable):
```bash
# MongoDB Secure Config
# Authentication
PUBLIC_PHRASE="admin"
PRIVATE_PHRASE="StrongPassword123!"
# Networking
APP_SERVER_IP="10.0.1.15"
# Backup Policy
BACKUP_RETENTION_DAYS=30
```
Secure it:
```bash
sudo chmod 600 /etc/mongo-secure.conf
sudo chown root:root /etc/mongo-secure.conf
```
---
# 🛡 Hardened MongoDB Setup Script (Config-Driven)
Save as `setup_secure_mongo.sh` and run with `sudo bash setup_secure_mongo.sh`:
```bash
#!/bin/bash
set -e
# ================================
# Load Config File
# ================================
CONFIG_FILE="/etc/mongo-secure.conf"
if [ ! -f "$CONFIG_FILE" ]; then
echo "[ERROR] Config file not found at $CONFIG_FILE"
exit 1
fi
# shellcheck disable=SC1090
source "$CONFIG_FILE"
# ================================
# Fixed Paths
# ================================
DB_PATH="/var/lib/mongodb"
LOG_PATH="/var/log/mongodb/mongod.log"
BACKUP_PATH="/var/backups/mongodb"
# ================================
# 1. Install MongoDB 3.4 (if not installed)
# ================================
if ! command -v mongod &> /dev/null; then
echo "[*] Installing MongoDB 3.4..."
apt-get update
apt-get install -y mongodb-org=3.4.24 mongodb-org-server=3.4.24 mongodb-org-shell=3.4.24 mongodb-org-mongos=3.4.24 mongodb-org-tools=3.4.24
fi
# ================================
# 2. Create paths & permissions
# ================================
mkdir -p $DB_PATH
mkdir -p $(dirname $LOG_PATH)
mkdir -p $BACKUP_PATH
chown -R mongodb:mongodb $DB_PATH $(dirname $LOG_PATH) $BACKUP_PATH
# ================================
# 3. Systemd service
# ================================
cat > /etc/systemd/system/mongod.service <<EOF
[Unit]
Description=MongoDB Database Server
After=network.target
[Service]
User=mongodb
Group=mongodb
ExecStart=/usr/bin/mongod --config /etc/mongod.conf
PIDFile=/var/run/mongodb/mongod.pid
LimitNOFILE=64000
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# ================================
# 4. Detect storage engine
# ================================
ENGINE=$(mongo --quiet --eval "db.serverStatus().storageEngine.name" 2>/dev/null || echo "unknown")
echo "[*] Detected storage engine: $ENGINE"
# ================================
# 5. MongoDB config (default to WiredTiger)
# ================================
cat > /etc/mongod.conf <<EOF
storage:
dbPath: $DB_PATH
journal:
enabled: true
engine: wiredTiger
systemLog:
destination: file
path: $LOG_PATH
logAppend: true
net:
port: 27017
bindIp: 127.0.0.1,$APP_SERVER_IP
security:
authorization: enabled
EOF
# ================================
# 6. If mmapv1, migrate to WiredTiger
# ================================
if [[ "$ENGINE" == "mmapv1" ]]; then
echo "[*] Migrating data from mmapv1 to WiredTiger..."
BACKUP_DIR="$BACKUP_PATH/migration-$(date +%F-%H%M)"
mkdir -p "$BACKUP_DIR"
mongodump --out "$BACKUP_DIR"
echo "[*] Backup created at $BACKUP_DIR"
systemctl stop mongod
mv $DB_PATH ${DB_PATH}-mmapv1-backup-$(date +%F-%H%M)
mkdir -p $DB_PATH
chown -R mongodb:mongodb $DB_PATH
systemctl start mongod
sleep 5
mongorestore "$BACKUP_DIR"
echo "[✔] Migration complete. Old mmapv1 data preserved at ${DB_PATH}-mmapv1-backup-*"
fi
# ================================
# 7. Firewall rules (iptables)
# ================================
echo "[*] Configuring iptables firewall..."
iptables -A INPUT -p tcp -s $APP_SERVER_IP --dport 27017 -j ACCEPT
iptables -A INPUT -p tcp --dport 27017 -j DROP
netfilter-persistent save || true
# ================================
# 8. Start MongoDB
# ================================
systemctl daemon-reexec
systemctl enable mongod
systemctl restart mongod
# ================================
# 9. Create Admin User
# ================================
echo "[*] Creating MongoDB admin user..."
mongo admin --eval "db.createUser({ user: '$PUBLIC_PHRASE', pwd: '$PRIVATE_PHRASE', roles:[{ role:'root', db:'admin'}] })" || true
# ================================
# 10. Setup log rotation
# ================================
cat > /etc/logrotate.d/mongodb <<EOF
$LOG_PATH {
weekly
rotate 4
compress
delaycompress
missingok
notifempty
create 640 mongodb mongodb
sharedscripts
postrotate
systemctl reload mongod > /dev/null
endscript
}
EOF
# ================================
# 11. Disk watchdog
# ================================
cat > /usr/local/bin/check_mongo_disk.sh <<'EOF'
#!/bin/bash
THRESHOLD=85
USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "Disk usage critical: $USAGE%, cleaning logs..."
journalctl --vacuum-size=200M
find /var/log/mongodb/ -type f -name "*.log" -size +50M -delete
find /var/backups/mongodb/ -type d -mtime +30 -exec rm -rf {} \; 2>/dev/null
fi
EOF
chmod +x /usr/local/bin/check_mongo_disk.sh
echo "*/15 * * * * root /usr/local/bin/check_mongo_disk.sh" >> /etc/crontab
# ================================
# 12. Mongo watchdog
# ================================
cat > /usr/local/bin/mongo_watchdog.sh <<'EOF'
#!/bin/bash
if ! pgrep mongod > /dev/null; then
echo "MongoDB down, restarting..." | mail -s "MongoDB Alert" admin@example.com
systemctl restart mongod
fi
EOF
chmod +x /usr/local/bin/mongo_watchdog.sh
echo "*/5 * * * * root /usr/local/bin/mongo_watchdog.sh" >> /etc/crontab
# ================================
# 13. Backup script (weekly + rotation)
# ================================
cat > /usr/local/bin/mongo_backup.sh <<EOF
#!/bin/bash
BACKUP_DIR="$BACKUP_PATH/mongo-\$(date +%F)"
mongodump --out \$BACKUP_DIR --username $PUBLIC_PHRASE --password $PRIVATE_PHRASE --authenticationDatabase admin
find $BACKUP_PATH -type d -mtime +$BACKUP_RETENTION_DAYS -exec rm -rf {} \;
EOF
chmod +x /usr/local/bin/mongo_backup.sh
echo "0 2 * * 0 root /usr/local/bin/mongo_backup.sh" >> /etc/crontab
# ================================
# Info for operator
# ================================
echo "[✔] MongoDB hardened setup complete!"
echo "Admin login: mongo -u $PUBLIC_PHRASE -p $PRIVATE_PHRASE --authenticationDatabase admin"
echo "Backups stored in: $BACKUP_PATH (rotated every $BACKUP_RETENTION_DAYS days)"
echo "Config stored in: $CONFIG_FILE"
```
---
### 🚀 Improvements
- **No sensitive values in script** (all in `/etc/mongo-secure.conf`).
- **Configurable public/private phrase** instead of hardcoded admin/password.
- **Configurable retention, IP whitelist** without editing the script.
- Script **auto-detects mmapv1 → migrates to WiredTiger** safely.
---
👉 Do you want me to also include **hashing or encryption** of the `PRIVATE_PHRASE` in the config file (so it’s not plain text), or keep it plaintext but root-only readable?
user
Can you make credential information as separate file. All sensitive settings that are not credential information should be group in another file. We want also to make these file location dynamic as we don't know where we will put it in the future. Can we make these file path taken from a source file but provide defaults?
ChatGPT
Got it 👍 — you want a **3-layer config setup**:
1. **Source file (main entry)** → defines *where the other config files live* (with defaults if not set).
2. **Credentials file** → holds only sensitive auth (public phrase, private phrase).
3. **Settings file** → holds other sensitive but non-credential values (app server IP, backup retention, etc).
This way you can move files anywhere and just update the source file. The script will stay the same.
---
# 🔑 File Structure
### `/etc/mongo-source.conf`
*(entry point with defaults — can be customized later)*
```bash
# Source Config (entry point)
# Default locations (override if needed)
CRED_FILE=${CRED_FILE:-/etc/mongo-cred.conf}
SETTINGS_FILE=${SETTINGS_FILE:-/etc/mongo-settings.conf}
```
---
### `/etc/mongo-cred.conf`
*(credentials only — STRICT permissions `600`)*
```bash
# MongoDB Credentials
PUBLIC_PHRASE="admin"
PRIVATE_PHRASE="SuperSecret123!"
```
---
### `/etc/mongo-settings.conf`
*(non-credential sensitive settings)*
```bash
# Networking
APP_SERVER_IP="10.0.1.15"
# Backup Policy
BACKUP_RETENTION_DAYS=30
```
---
# 🛡 Hardened Setup Script (Config-Layered)
```bash
#!/bin/bash
set -e
# ================================
# 1. Load source config (with defaults)
# ================================
SOURCE_FILE="/etc/mongo-source.conf"
if [ -f "$SOURCE_FILE" ]; then
# shellcheck disable=SC1090
source "$SOURCE_FILE"
else
echo "[WARN] Source config not found, using defaults"
CRED_FILE="/etc/mongo-cred.conf"
SETTINGS_FILE="/etc/mongo-settings.conf"
fi
# Load credentials
if [ -f "$CRED_FILE" ]; then
# shellcheck disable=SC1090
source "$CRED_FILE"
else
echo "[ERROR] Credentials file not found: $CRED_FILE"
exit 1
fi
# Load settings
if [ -f "$SETTINGS_FILE" ]; then
# shellcheck disable=SC1090
source "$SETTINGS_FILE"
else
echo "[ERROR] Settings file not found: $SETTINGS_FILE"
exit 1
fi
# ================================
# 2. Fixed Paths
# ================================
DB_PATH="/var/lib/mongodb"
LOG_PATH="/var/log/mongodb/mongod.log"
BACKUP_PATH="/var/backups/mongodb"
# Create dirs
mkdir -p $DB_PATH $BACKUP_PATH $(dirname $LOG_PATH)
chown -R mongodb:mongodb $DB_PATH $BACKUP_PATH $(dirname $LOG_PATH)
# ================================
# 3. Detect storage engine
# ================================
ENGINE=$(mongo --quiet --eval "db.serverStatus().storageEngine.name" 2>/dev/null || echo "unknown")
echo "[*] Detected storage engine: $ENGINE"
# ================================
# 4. MongoDB Config
# ================================
cat > /etc/mongod.conf <<EOF
storage:
dbPath: $DB_PATH
journal:
enabled: true
engine: wiredTiger
systemLog:
destination: file
path: $LOG_PATH
logAppend: true
net:
port: 27017
bindIp: 127.0.0.1,$APP_SERVER_IP
security:
authorization: enabled
EOF
# ================================
# 5. Auto-migrate mmapv1 → WiredTiger
# ================================
if [[ "$ENGINE" == "mmapv1" ]]; then
echo "[*] Migrating mmapv1 → WiredTiger..."
BACKUP_DIR="$BACKUP_PATH/migration-$(date +%F-%H%M)"
mongodump --out "$BACKUP_DIR"
systemctl stop mongod
mv $DB_PATH ${DB_PATH}-mmapv1-backup-$(date +%F-%H%M)
mkdir -p $DB_PATH
chown -R mongodb:mongodb $DB_PATH
systemctl start mongod
sleep 5
mongorestore "$BACKUP_DIR"
echo "[✔] Migration complete. Old mmapv1 data preserved."
fi
# ================================
# 6. Firewall (iptables)
# ================================
iptables -A INPUT -p tcp -s $APP_SERVER_IP --dport 27017 -j ACCEPT
iptables -A INPUT -p tcp --dport 27017 -j DROP
netfilter-persistent save || true
# ================================
# 7. Create Admin User
# ================================
mongo admin --eval "db.createUser({ user: '$PUBLIC_PHRASE', pwd: '$PRIVATE_PHRASE', roles:[{ role:'root', db:'admin'}] })" || true
# ================================
# 8. Backup Job
# ================================
cat > /usr/local/bin/mongo_backup.sh <<EOF
#!/bin/bash
BACKUP_DIR="$BACKUP_PATH/mongo-\$(date +%F)"
mongodump --out \$BACKUP_DIR --username $PUBLIC_PHRASE --password $PRIVATE_PHRASE --authenticationDatabase admin
find $BACKUP_PATH -type d -mtime +$BACKUP_RETENTION_DAYS -exec rm -rf {} \;
EOF
chmod +x /usr/local/bin/mongo_backup.sh
echo "0 2 * * 0 root /usr/local/bin/mongo_backup.sh" >> /etc/crontab
echo "[✔] Setup complete. Config layered for flexibility."
```
---
### 🔐 Security Notes
- `CRED_FILE` (`mongo-cred.conf`) → should be `chmod 600`, root-only.
- `SETTINGS_FILE` can be group-readable but best practice is also `600`.
- `SOURCE_FILE` just points to locations, can be less strict.
---
👉 Would you like me to also add a **feature that encrypts the `PRIVATE_PHRASE`** in `mongo-cred.conf` (e.g., using `openssl` or `gpg`) and decrypts it at runtime, or keep it as plaintext but root-only readable?
Certificate authentication in MongoDB
user
is it possible to use certificate instead of password when running a mongodb server?
ChatGPT
Yes ✅, it’s possible to use **certificates instead of passwords** when running a MongoDB server. This is typically done using **x.509 certificate-based authentication**, which is supported in MongoDB Enterprise and Community editions.
Here’s the breakdown:
---
### 🔑 How it Works
- MongoDB can authenticate clients (users) and servers (in replica sets or sharded clusters) using **x.509 certificates** issued by a trusted Certificate Authority (CA).
- Instead of providing a **username/password**, the client presents a certificate.
- The server validates the certificate’s subject (DN) against an authorized user in MongoDB.
---
### ⚙️ Steps to Enable Certificate Authentication
1. **Generate Certificates**
- Create a CA (or use an existing one).
- Generate server and client certificates signed by the CA.
- Ensure the certificates include the **Distinguished Name (DN)** in the `subject`.
2. **Configure MongoDB Server**
```yaml
# mongod.conf
net:
ssl:
mode: requireSSL
PEMKeyFile: /etc/ssl/mongodb.pem
CAFile: /etc/ssl/ca.pem
security:
authorization: enabled
clusterAuthMode: x509
```
Key options:
- `mode: requireSSL` → All connections must use TLS/SSL.
- `PEMKeyFile` → The server’s certificate + private key.
- `CAFile` → The trusted CA file.
- `clusterAuthMode: x509` → Required for certificate-based authentication.
3. **Create a MongoDB User with x.509**
- Connect with an **admin client certificate**.
- Create a user whose name matches the certificate’s DN:
```js
use $external
db.createUser({
user: "CN=myclient.example.com,OU=OrgUnit,O=MyOrg,L=City,ST=State,C=US",
roles: [ { role: "readWrite", db: "test" } ]
})
```
> Notice: the `user` field must match the **subject DN** in the client’s certificate.
4. **Connect with a Client Using Certificate**
```bash
mongo --host <hostname> \
--ssl \
--sslCAFile /etc/ssl/ca.pem \
--sslPEMKeyFile /etc/ssl/client.pem \
--authenticationMechanism MONGODB-X509
```
---
### 🔒 Benefits
- No password leaks possible.
- Stronger mutual authentication (both server and client validate each other).
- Integrates with enterprise PKI setups.
---
👉 Would you like me to prepare a **step-by-step guide for Ubuntu** (certificate generation + MongoDB config + client connection), so you can test this locally?
user
can we use local certificates or letsencrypt for this?
ChatGPT
Great question 👍
Yes — you can use **either local self-signed certificates** or **public CA certificates like Let’s Encrypt** for MongoDB’s x.509 authentication, but with different implications:
---
## 🔹 1. Local / Self-Signed Certificates
- Easiest for **development and internal/private networks**.
- You generate your own **CA** and issue certs for the server and clients.
- MongoDB trusts that CA (`CAFile` in config).
- No internet validation required.
- Downside: you need to securely distribute and trust your CA cert manually on every client machine.
👉 Best choice if you’re running MongoDB only inside a LAN, Docker, or a private cloud.
---
## 🔹 2. Let’s Encrypt Certificates
- Let’s Encrypt is a **public CA** that issues free TLS certs for domain names.
- You can use Let’s Encrypt for:
- **Server TLS encryption** → MongoDB accepts secure connections with a valid, publicly trusted cert.
- **Optional client authentication** if you generate **client certs signed by your own internal CA**, while still using Let’s Encrypt for the server cert.
⚠️ Important:
- Let’s Encrypt certs are **valid only for servers with public DNS names** (not private IPs like `192.168.x.x` or `localhost`).
- LE certificates expire every **90 days**, so you need to automate renewal (e.g., with `certbot`).
👉 This is useful if you want external clients to securely connect to MongoDB over the internet.
---
## 🔑 Typical Setup (Hybrid)
- **Server cert**: Issued by Let’s Encrypt (so clients automatically trust the server).
- **Client certs**: Issued by your own internal CA (so only your apps can authenticate).
- MongoDB configured with both:
```yaml
net:
ssl:
mode: requireSSL
PEMKeyFile: /etc/letsencrypt/live/mongo.example.com/fullchain.pem
PEMKeyPassword: <if any>
CAFile: /etc/ssl/myCA.pem # your internal CA that signed client certs
security:
clusterAuthMode: x509
authorization: enabled
```
---
✅ So:
- For **local dev/testing** → use self-signed certs.
- For **production / internet-facing MongoDB** → use Let’s Encrypt for the server, and your own CA for client certs.
---
Do you want me to give you a **step-by-step Ubuntu guide** for setting up MongoDB with:
1. **Self-signed local CA certs** (private LAN use), and
2. **Let’s Encrypt certs** (internet-facing setup with `certbot`)?
user
Can you create a script to automate the number 2 solution.
ChatGPT
Got it 👍 — you want an **automation script for Ubuntu** that sets up **MongoDB with Let’s Encrypt (for the server cert)** and a **local CA (for client x.509 auth)**.
Here’s a Bash script that:
1. Installs **certbot** and generates Let’s Encrypt certificates for your MongoDB domain.
2. Generates a **local CA** and issues **client certificates**.
3. Configures MongoDB to use the Let’s Encrypt server cert + internal CA for client authentication.
4. Sets up a **systemd service hook** to reload MongoDB when certs renew.
---
### 🔧 Script: `setup-mongo-cert-auth.sh`
```bash
#!/bin/bash
set -e
# --- CONFIGURATION ---
MONGO_DOMAIN="mongo.example.com" # Change to your public domain pointing to MongoDB
MONGO_CONF="/etc/mongod.conf"
LE_PATH="/etc/letsencrypt/live/$MONGO_DOMAIN"
CA_DIR="/etc/mongoCA"
CLIENT_DIR="/etc/mongoCA/clients"
MONGO_USER="mongodb"
# --- 1. Install dependencies ---
echo "[*] Installing certbot and openssl..."
apt-get update
apt-get install -y certbot openssl
# --- 2. Generate Let's Encrypt server certificate ---
echo "[*] Generating Let's Encrypt certificate for $MONGO_DOMAIN ..."
certbot certonly --standalone -d "$MONGO_DOMAIN" --non-interactive --agree-tos -m admin@"$MONGO_DOMAIN"
# --- 3. Create a local CA for client certs ---
echo "[*] Creating local CA..."
mkdir -p "$CA_DIR" "$CLIENT_DIR"
cd "$CA_DIR"
if [ ! -f ca.key ]; then
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=DB/CN=MongoCA" \
-out ca.pem
fi
# --- 4. Generate client certificate (example client: app1) ---
CLIENT_NAME="app1"
echo "[*] Generating client certificate for $CLIENT_NAME..."
openssl genrsa -out "$CLIENT_DIR/$CLIENT_NAME.key" 2048
openssl req -new -key "$CLIENT_DIR/$CLIENT_NAME.key" \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=Client/CN=$CLIENT_NAME" \
-out "$CLIENT_DIR/$CLIENT_NAME.csr"
openssl x509 -req -in "$CLIENT_DIR/$CLIENT_NAME.csr" \
-CA ca.pem -CAkey ca.key -CAcreateserial \
-out "$CLIENT_DIR/$CLIENT_NAME.pem" -days 365 -sha256
# --- 5. Backup existing mongod.conf ---
echo "[*] Backing up MongoDB config..."
cp "$MONGO_CONF" "$MONGO_CONF.bak.$(date +%F-%H%M%S)"
# --- 6. Configure MongoDB for SSL + x.509 ---
echo "[*] Updating MongoDB config..."
cat > "$MONGO_CONF" <<EOF
net:
ssl:
mode: requireSSL
PEMKeyFile: $LE_PATH/fullchain.pem
PEMKeyPassword: ""
CAFile: $CA_DIR/ca.pem
security:
clusterAuthMode: x509
authorization: enabled
EOF
# --- 7. Restart MongoDB ---
echo "[*] Restarting MongoDB..."
systemctl restart mongod
# --- 8. Setup auto-reload on cert renew ---
echo "[*] Setting up systemd reload hook for MongoDB..."
cat > /etc/systemd/system/certbot-renew-mongo.service <<EOF
[Unit]
Description=Reload MongoDB after cert renewal
[Service]
Type=oneshot
ExecStart=/bin/systemctl restart mongod
EOF
cat > /etc/systemd/system/certbot-renew-mongo.timer <<EOF
[Unit]
Description=Run MongoDB reload after certbot renew
[Timer]
OnCalendar=weekly
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl enable certbot-renew-mongo.timer
systemctl start certbot-renew-mongo.timer
echo "[✔] Setup complete!"
echo "Client certificate for $CLIENT_NAME stored in $CLIENT_DIR"
echo "Import this DN into MongoDB:"
echo " use \$external"
echo " db.createUser({user: 'CN=$CLIENT_NAME,OU=Client,O=MyOrg,L=City,ST=State,C=US', roles:[{role:'readWrite',db:'test'}]})"
```
---
### 🔑 After Running the Script
1. Ensure `mongo.example.com` points to your server’s public IP.
2. MongoDB will now:
- Use **Let’s Encrypt certs** for TLS.
- Trust only clients with **your internal CA certs**.
3. Client connects with:
```bash
mongo --host mongo.example.com \
--ssl \
--sslCAFile /etc/mongoCA/ca.pem \
--sslPEMKeyFile /etc/mongoCA/clients/app1.pem \
--authenticationMechanism MONGODB-X509
```
---
⚠️ Notes:
- Replace `mongo.example.com` with your real domain.
- Open MongoDB port (default `27017`) in firewall.
- Let’s Encrypt requires **port 80** to be open temporarily during certificate issuance.
---
👉 Do you want me to also make a **companion script for generating new client certificates on demand** (e.g., `add-mongo-client.sh <clientName>`)? That way you don’t need to modify the main setup script again.
user
Instead of generating new client certificate on demand, can you modify the script to include monthly renewal of all certificates so that we can prevent issues when it expires. Can you also harden the certificate to expire within 3 months only.
ChatGPT
Perfect 👍 you’re thinking like a sysadmin:
- **Let’s Encrypt certs** already expire every **90 days** (≈3 months).
- We’ll **force client certificates** to also expire every **90 days** for consistency.
- We’ll add a **monthly renewal job** that:
1. Runs `certbot renew` (for server cert).
2. Regenerates **all client certs** from your local CA with 90-day validity.
3. Restarts MongoDB so it reloads fresh certs.
---
## 🔧 Updated Script: `setup-mongo-cert-auth.sh`
```bash
#!/bin/bash
set -e
# --- CONFIGURATION ---
MONGO_DOMAIN="mongo.example.com" # Change this to your real domain
MONGO_CONF="/etc/mongod.conf"
LE_PATH="/etc/letsencrypt/live/$MONGO_DOMAIN"
CA_DIR="/etc/mongoCA"
CLIENT_DIR="/etc/mongoCA/clients"
MONGO_USER="mongodb"
# --- 1. Install dependencies ---
echo "[*] Installing certbot and openssl..."
apt-get update
apt-get install -y certbot openssl
# --- 2. Generate Let's Encrypt server certificate ---
echo "[*] Generating Let's Encrypt certificate for $MONGO_DOMAIN ..."
certbot certonly --standalone -d "$MONGO_DOMAIN" --non-interactive --agree-tos -m admin@"$MONGO_DOMAIN"
# --- 3. Create local CA for client certs ---
echo "[*] Creating local CA..."
mkdir -p "$CA_DIR" "$CLIENT_DIR"
cd "$CA_DIR"
if [ ! -f ca.key ]; then
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=DB/CN=MongoCA" \
-out ca.pem
fi
# --- 4. Function: Generate client cert ---
generate_client_cert() {
local CLIENT_NAME=$1
echo "[*] Generating client certificate for $CLIENT_NAME..."
openssl genrsa -out "$CLIENT_DIR/$CLIENT_NAME.key" 2048
openssl req -new -key "$CLIENT_DIR/$CLIENT_NAME.key" \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=Client/CN=$CLIENT_NAME" \
-out "$CLIENT_DIR/$CLIENT_NAME.csr"
# Expire in 90 days max
openssl x509 -req -in "$CLIENT_DIR/$CLIENT_NAME.csr" \
-CA ca.pem -CAkey ca.key -CAcreateserial \
-out "$CLIENT_DIR/$CLIENT_NAME.pem" -days 90 -sha256
}
# --- 5. Generate default client cert (app1) ---
generate_client_cert "app1"
# --- 6. Backup existing mongod.conf ---
echo "[*] Backing up MongoDB config..."
cp "$MONGO_CONF" "$MONGO_CONF.bak.$(date +%F-%H%M%S)" || true
# --- 7. Configure MongoDB for SSL + x.509 ---
echo "[*] Updating MongoDB config..."
cat > "$MONGO_CONF" <<EOF
net:
ssl:
mode: requireSSL
PEMKeyFile: $LE_PATH/fullchain.pem
PEMKeyPassword: ""
CAFile: $CA_DIR/ca.pem
security:
clusterAuthMode: x509
authorization: enabled
EOF
# --- 8. Restart MongoDB ---
echo "[*] Restarting MongoDB..."
systemctl restart mongod
# --- 9. Create renewal script ---
RENEW_SCRIPT="/usr/local/bin/mongo-cert-renew.sh"
cat > "$RENEW_SCRIPT" <<'EOF'
#!/bin/bash
set -e
MONGO_DOMAIN="mongo.example.com"
LE_PATH="/etc/letsencrypt/live/$MONGO_DOMAIN"
CA_DIR="/etc/mongoCA"
CLIENT_DIR="/etc/mongoCA/clients"
echo "[*] Renewing Let's Encrypt server certificate..."
certbot renew --quiet
echo "[*] Regenerating client certificates..."
for csr in "$CLIENT_DIR"/*.csr; do
CLIENT_NAME=$(basename "$csr" .csr)
openssl x509 -req -in "$CLIENT_DIR/$CLIENT_NAME.csr" \
-CA "$CA_DIR/ca.pem" -CAkey "$CA_DIR/ca.key" -CAcreateserial \
-out "$CLIENT_DIR/$CLIENT_NAME.pem" -days 90 -sha256
done
echo "[*] Restarting MongoDB to reload certs..."
systemctl restart mongod
EOF
chmod +x "$RENEW_SCRIPT"
# --- 10. Setup systemd timer for monthly renewal ---
echo "[*] Setting up monthly renewal via systemd..."
cat > /etc/systemd/system/mongo-cert-renew.service <<EOF
[Unit]
Description=Renew MongoDB certs (server + clients)
[Service]
Type=oneshot
ExecStart=$RENEW_SCRIPT
EOF
cat > /etc/systemd/system/mongo-cert-renew.timer <<EOF
[Unit]
Description=Run MongoDB certificate renewal monthly
[Timer]
OnCalendar=monthly
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl enable mongo-cert-renew.timer
systemctl start mongo-cert-renew.timer
echo "[✔] Setup complete!"
echo "Client certificate for app1 stored in $CLIENT_DIR"
echo "Import this DN into MongoDB:"
echo " use \$external"
echo " db.createUser({user: 'CN=app1,OU=Client,O=MyOrg,L=City,ST=State,C=US', roles:[{role:'readWrite',db:'test'}]})"
```
---
## 🔑 What This Script Does
1. **Let’s Encrypt server certs** → managed by `certbot`, auto-renewed every 90 days.
2. **Client certs** → regenerated monthly, each with **90-day expiry**.
3. **MongoDB** restarts automatically after renewals, so it always runs with fresh certs.
---
✅ This way, you’ll **never run into expired certs** unless MongoDB fails to restart.
---
Would you like me to extend this so that when the **monthly renewal runs**, it also **emails you (or posts to Slack/Telegram)** if renewal fails, so you know before clients break?
user
Is it possible that when accessing the MongoDB via ssh or terminal we will use certificate but when accessing the database via mongoose or nodejs we will use username and password which can only be setup by admins who accessed via ssh or terminal and ensure that nobody can create additional users except those with access via certificate?
ChatGPT
Yes, that’s possible ✅ and it’s actually a **common pattern in production MongoDB setups**:
- **Admins** authenticate with **x.509 certificates** (strong authentication, usually only accessible via SSH/terminal).
- **Applications** (e.g., via Mongoose/Node.js) authenticate with **username/password** accounts created and controlled only by the certificate-authenticated admins.
- You lock down the system so **only cert-authenticated users can create/manage new accounts**.
---
## 🔹 How to Enforce This
### 1. Enable x.509 for Admins
- Configure MongoDB with **requireSSL + x.509 auth** (as in our script).
- Create an **admin user** under `$external` authentication DB using the DN from the certificate:
```js
use $external
db.createUser({
user: "CN=AdminUser,OU=DB,O=MyOrg,L=City,ST=State,C=US",
roles: [
{ role: "userAdminAnyDatabase", db: "admin" },
{ role: "dbAdminAnyDatabase", db: "admin" },
{ role: "readWriteAnyDatabase", db: "admin" }
]
})
```
🔑 This admin can only log in with the **certificate**. No password exists.
---
### 2. Create Application Accounts (Username/Password)
- While logged in as the **cert-based admin**, create normal database users (for apps):
```js
use myappdb
db.createUser({
user: "appuser",
pwd: "strongpassword123",
roles: [ { role: "readWrite", db: "myappdb" } ]
})
```
- Apps connect via Mongoose like normal:
```js
mongoose.connect("mongodb://appuser:strongpassword123@mongo.example.com:27017/myappdb?ssl=true");
```
---
### 3. Prevent Unauthorized User Creation
- **Disable `createUser` and `grantRolesToUser` for password-based accounts** by using **role-based access control (RBAC)**.
- By default, only users with `userAdmin` or `userAdminAnyDatabase` can create new accounts.
- So, simply: **don’t give that role to any password-based user**.
- Your password-based accounts (like `appuser`) should only have `readWrite` or `readWriteAnyDatabase`.
---
### 4. Enforce "Admin via Certificate Only"
To make sure **only certificate-authenticated users can create/manage other users**:
- In MongoDB:
- The **only accounts with admin roles** (userAdmin, root, dbAdmin) must be created under `$external` with x.509 certs.
- All **password accounts** are created with minimal roles (`readWrite`, `read`) only.
- In practice:
```js
// ❌ never do this for a password account
db.grantRolesToUser("appuser", [{ role: "userAdminAnyDatabase", db: "admin" }])
```
- If someone compromises a password user, they **cannot escalate** because they don’t have `userAdmin` roles.
---
### 🔒 Security Flow Recap
- **SSH/Terminal (Admins)** → login with MongoDB **cert-based x.509** → can create/modify users.
- **Applications (Node.js/Mongoose)** → login with **username/password** → limited DB access only.
- **Separation of duties** → Only cert-authenticated admins manage accounts. No password-based user can create another user.
---
✅ This is fully supported by MongoDB’s **RBAC model** and x.509 authentication.
It’s also aligned with security best practices:
- **Admins use certificates** (no passwords to leak).
- **Apps use passwords** (easy integration, limited scope).
---
Would you like me to **extend the automation script** we made so it:
1. Creates a **cert-only admin user** (for SSH/terminal).
2. Blocks any password account from having admin rights.
3. Provides a helper command for admins to create new app accounts?
user
We are currently using mongodb 3.4.24 can you make these solutions automated that we can only run in one script?
ChatGPT
Thanks for clarifying 👍. Since you’re on **MongoDB 3.4.24**, there are a few important points:
- **x.509 authentication** is already supported in 3.4 (introduced in 2.6).
- RBAC (role-based access control) is also supported.
- MongoDB 3.4 is **end-of-life (EOL)** — so no more patches, but your requirements are still doable.
- The configs are slightly different (SSL options instead of `net.tls` used in newer versions).
---
## 🎯 What the “One Script” Should Do
When you run it once on Ubuntu, it should:
1. Install MongoDB dependencies (skip if already installed).
2. Generate a Let’s Encrypt cert for the **server**.
3. Generate a **local CA + client certs** for **admins**.
4. Configure MongoDB (`mongod.conf`) to:
- Require SSL,
- Accept Let’s Encrypt server cert,
- Trust local CA for client x.509.
5. Start MongoDB.
6. Create an **admin user authenticated by certificate only**.
7. Configure roles so **only that admin** can create new users.
8. Create an **example app user (username/password)** with limited rights.
9. Set up **monthly renewal** of server + client certs.
---
## 🔧 Automated Script for MongoDB 3.4.24
Save as: `setup-mongo34-secure.sh`
```bash
#!/bin/bash
set -e
# --- CONFIGURATION ---
MONGO_DOMAIN="mongo.example.com" # <-- Change this to your public domain
MONGO_CONF="/etc/mongod.conf"
LE_PATH="/etc/letsencrypt/live/$MONGO_DOMAIN"
CA_DIR="/etc/mongoCA"
CLIENT_DIR="/etc/mongoCA/clients"
ADMIN_CN="MongoAdmin" # CN for admin cert
APP_USER="appuser"
APP_PASS="StrongPassword123"
# --- 1. Install dependencies ---
echo "[*] Installing certbot and openssl..."
apt-get update
apt-get install -y certbot openssl
# --- 2. Get Let's Encrypt server certificate ---
echo "[*] Requesting Let's Encrypt certificate for $MONGO_DOMAIN ..."
certbot certonly --standalone -d "$MONGO_DOMAIN" --non-interactive --agree-tos -m admin@"$MONGO_DOMAIN"
# --- 3. Create local CA ---
echo "[*] Creating local CA..."
mkdir -p "$CA_DIR" "$CLIENT_DIR"
cd "$CA_DIR"
if [ ! -f ca.key ]; then
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=DB/CN=MongoCA" \
-out ca.pem
fi
# --- 4. Generate admin client cert (90 days) ---
echo "[*] Generating admin client certificate..."
openssl genrsa -out "$CLIENT_DIR/admin.key" 2048
openssl req -new -key "$CLIENT_DIR/admin.key" \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=DB/CN=$ADMIN_CN" \
-out "$CLIENT_DIR/admin.csr"
openssl x509 -req -in "$CLIENT_DIR/admin.csr" \
-CA ca.pem -CAkey ca.key -CAcreateserial \
-out "$CLIENT_DIR/admin.pem" -days 90 -sha256
# --- 5. Backup mongod.conf ---
echo "[*] Backing up mongod.conf..."
cp "$MONGO_CONF" "$MONGO_CONF.bak.$(date +%F-%H%M%S)" || true
# --- 6. Configure MongoDB for SSL ---
echo "[*] Updating MongoDB config..."
cat > "$MONGO_CONF" <<EOF
net:
ssl:
mode: requireSSL
PEMKeyFile: $LE_PATH/fullchain.pem
PEMKeyPassword: ""
CAFile: $CA_DIR/ca.pem
security:
clusterAuthMode: x509
authorization: enabled
EOF
# --- 7. Restart MongoDB ---
echo "[*] Restarting MongoDB..."
systemctl restart mongod
# --- 8. Create Admin User (x.509 only) ---
echo "[*] Creating x.509 admin user..."
mongo --ssl \
--sslCAFile "$CA_DIR/ca.pem" \
--sslPEMKeyFile "$CLIENT_DIR/admin.pem" \
--host "$MONGO_DOMAIN" \
--eval "db.getSiblingDB('\$external').createUser({
user: 'CN=$ADMIN_CN,OU=DB,O=MyOrg,L=City,ST=State,C=US',
roles: [
{role:'userAdminAnyDatabase', db:'admin'},
{role:'dbAdminAnyDatabase', db:'admin'},
{role:'readWriteAnyDatabase', db:'admin'}
]
})"
# --- 9. Create App User (username/password, no admin rights) ---
echo "[*] Creating restricted app user..."
mongo --ssl \
--sslCAFile "$CA_DIR/ca.pem" \
--sslPEMKeyFile "$CLIENT_DIR/admin.pem" \
--host "$MONGO_DOMAIN" \
--eval "db.getSiblingDB('myappdb').createUser({
user: '$APP_USER',
pwd: '$APP_PASS',
roles: [{role:'readWrite', db:'myappdb'}]
})"
# --- 10. Create monthly renewal script ---
echo "[*] Setting up monthly renewal..."
RENEW_SCRIPT="/usr/local/bin/mongo-cert-renew.sh"
cat > "$RENEW_SCRIPT" <<'EOF'
#!/bin/bash
set -e
MONGO_DOMAIN="mongo.example.com"
LE_PATH="/etc/letsencrypt/live/$MONGO_DOMAIN"
CA_DIR="/etc/mongoCA"
CLIENT_DIR="/etc/mongoCA/clients"
echo "[*] Renewing Let's Encrypt cert..."
certbot renew --quiet
echo "[*] Renewing admin cert..."
openssl genrsa -out "$CLIENT_DIR/admin.key" 2048
openssl req -new -key "$CLIENT_DIR/admin.key" \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=DB/CN=MongoAdmin" \
-out "$CLIENT_DIR/admin.csr"
openssl x509 -req -in "$CLIENT_DIR/admin.csr" \
-CA "$CA_DIR/ca.pem" -CAkey "$CA_DIR/ca.key" -CAcreateserial \
-out "$CLIENT_DIR/admin.pem" -days 90 -sha256
echo "[*] Restarting MongoDB..."
systemctl restart mongod
EOF
chmod +x "$RENEW_SCRIPT"
cat > /etc/systemd/system/mongo-cert-renew.service <<EOF
[Unit]
Description=Renew MongoDB certs
[Service]
Type=oneshot
ExecStart=$RENEW_SCRIPT
EOF
cat > /etc/systemd/system/mongo-cert-renew.timer <<EOF
[Unit]
Description=Run MongoDB cert renew monthly
[Timer]
OnCalendar=monthly
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl enable mongo-cert-renew.timer
systemctl start mongo-cert-renew.timer
echo "[✔] Setup complete!"
echo "Admin cert: $CLIENT_DIR/admin.pem"
echo "App user: $APP_USER / $APP_PASS"
echo "Only x.509 admin can create new users."
```
---
## 🔑 What This Script Achieves
- **Server cert** = Let’s Encrypt (auto-renew every 90 days).
- **Admin cert** = Local CA, expires every 90 days, renewed monthly.
- **Admin account** = x.509 only, with full privileges.
- **App account** = username/password, `readWrite` only (no user creation).
- **Monthly renewal** keeps everything fresh.
---
⚠️ Important:
- MongoDB 3.4 still uses `net.ssl.*` config keys, not `net.tls.*`. I adjusted for that.
- Admin DN must match exactly with the cert CN (`CN=MongoAdmin,...`).
- If renewal generates a new admin cert, you’ll need to reconnect with the updated file.
---
👉 Do you want me to also extend this so that **when the admin cert is renewed**, the script automatically **replaces the old user with the new DN** in MongoDB (so you don’t have to run manual cleanup)?
user
Yes please, modify the script such that when certificate is renewed, all old users will be renewed also and it should not affect any applications connecting to the server via username and password
ChatGPT
Perfect 👍 I understand — you want **zero downtime for apps using username/password**, while still rotating all **x.509 admin certs** every 90 days.
Here’s how we’ll handle it:
- **Password-based users** (like `appuser`) → unchanged. Their accounts are not touched by renewal.
- **Certificate-based users (admins)** → reissued every 90 days.
- On renewal:
1. Generate a fresh admin certificate.
2. Extract the **new DN** from the cert.
3. Remove old x.509 admin users.
4. Re-create them with the **new DN**.
5. Restart MongoDB to apply.
This way:
- Apps keep working with username/password.
- Admins keep working, but only need to use the **new cert** file (renewed monthly).
---
## 🔧 Modified Script for MongoDB 3.4.24
Save as `setup-mongo34-secure.sh` (replaces the previous one).
```bash
#!/bin/bash
set -e
# --- CONFIGURATION ---
MONGO_DOMAIN="mongo.example.com" # <-- Change this to your real domain
MONGO_CONF="/etc/mongod.conf"
LE_PATH="/etc/letsencrypt/live/$MONGO_DOMAIN"
CA_DIR="/etc/mongoCA"
CLIENT_DIR="/etc/mongoCA/clients"
ADMIN_CN="MongoAdmin" # CN for admin cert
APP_USER="appuser"
APP_PASS="StrongPassword123"
# --- 1. Install dependencies ---
echo "[*] Installing certbot and openssl..."
apt-get update
apt-get install -y certbot openssl jq
# --- 2. Get Let's Encrypt server certificate ---
echo "[*] Requesting Let's Encrypt certificate for $MONGO_DOMAIN ..."
certbot certonly --standalone -d "$MONGO_DOMAIN" --non-interactive --agree-tos -m admin@"$MONGO_DOMAIN"
# --- 3. Create local CA ---
echo "[*] Creating local CA..."
mkdir -p "$CA_DIR" "$CLIENT_DIR"
cd "$CA_DIR"
if [ ! -f ca.key ]; then
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=DB/CN=MongoCA" \
-out ca.pem
fi
# --- 4. Function to generate admin cert and add to MongoDB ---
generate_admin_cert() {
local ADMIN_NAME=$1
echo "[*] Generating admin client certificate ($ADMIN_NAME)..."
openssl genrsa -out "$CLIENT_DIR/$ADMIN_NAME.key" 2048
openssl req -new -key "$CLIENT_DIR/$ADMIN_NAME.key" \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=DB/CN=$ADMIN_NAME" \
-out "$CLIENT_DIR/$ADMIN_NAME.csr"
openssl x509 -req -in "$CLIENT_DIR/$ADMIN_NAME.csr" \
-CA ca.pem -CAkey ca.key -CAcreateserial \
-out "$CLIENT_DIR/$ADMIN_NAME.pem" -days 90 -sha256
local DN="CN=$ADMIN_NAME,OU=DB,O=MyOrg,L=City,ST=State,C=US"
echo "[*] Creating MongoDB admin user for DN: $DN"
mongo --ssl \
--sslCAFile "$CA_DIR/ca.pem" \
--sslPEMKeyFile "$CLIENT_DIR/$ADMIN_NAME.pem" \
--host "$MONGO_DOMAIN" \
--eval "db.getSiblingDB('\$external').createUser({
user: '$DN',
roles: [
{role:'userAdminAnyDatabase', db:'admin'},
{role:'dbAdminAnyDatabase', db:'admin'},
{role:'readWriteAnyDatabase', db:'admin'}
]
})"
}
# --- 5. Backup mongod.conf ---
echo "[*] Backing up mongod.conf..."
cp "$MONGO_CONF" "$MONGO_CONF.bak.$(date +%F-%H%M%S)" || true
# --- 6. Configure MongoDB for SSL ---
echo "[*] Updating MongoDB config..."
cat > "$MONGO_CONF" <<EOF
net:
ssl:
mode: requireSSL
PEMKeyFile: $LE_PATH/fullchain.pem
PEMKeyPassword: ""
CAFile: $CA_DIR/ca.pem
security:
clusterAuthMode: x509
authorization: enabled
EOF
# --- 7. Restart MongoDB ---
echo "[*] Restarting MongoDB..."
systemctl restart mongod
# --- 8. Initial admin user ---
generate_admin_cert "$ADMIN_CN"
# --- 9. Create App User (username/password, no admin rights) ---
echo "[*] Creating restricted app user..."
mongo --ssl \
--sslCAFile "$CA_DIR/ca.pem" \
--sslPEMKeyFile "$CLIENT_DIR/$ADMIN_CN.pem" \
--host "$MONGO_DOMAIN" \
--eval "db.getSiblingDB('myappdb').createUser({
user: '$APP_USER',
pwd: '$APP_PASS',
roles: [{role:'readWrite', db:'myappdb'}]
})"
# --- 10. Renewal script ---
echo "[*] Creating renewal script..."
RENEW_SCRIPT="/usr/local/bin/mongo-cert-renew.sh"
cat > "$RENEW_SCRIPT" <<'EOF'
#!/bin/bash
set -e
MONGO_DOMAIN="mongo.example.com"
CA_DIR="/etc/mongoCA"
CLIENT_DIR="/etc/mongoCA/clients"
ADMIN_CN="MongoAdmin"
echo "[*] Renewing Let's Encrypt server cert..."
certbot renew --quiet
echo "[*] Generating new admin cert..."
NEW_NAME="$ADMIN_CN-$(date +%Y%m%d)"
openssl genrsa -out "$CLIENT_DIR/$NEW_NAME.key" 2048
openssl req -new -key "$CLIENT_DIR/$NEW_NAME.key" \
-subj "/C=US/ST=State/L=City/O=MyOrg/OU=DB/CN=$NEW_NAME" \
-out "$CLIENT_DIR/$NEW_NAME.csr"
openssl x509 -req -in "$CLIENT_DIR/$NEW_NAME.csr" \
-CA "$CA_DIR/ca.pem" -CAkey "$CA_DIR/ca.key" -CAcreateserial \
-out "$CLIENT_DIR/$NEW_NAME.pem" -days 90 -sha256
NEW_DN="CN=$NEW_NAME,OU=DB,O=MyOrg,L=City,ST=State,C=US"
echo "[*] Adding new admin user with DN $NEW_DN..."
mongo --ssl \
--sslCAFile "$CA_DIR/ca.pem" \
--sslPEMKeyFile "$CLIENT_DIR/$NEW_NAME.pem" \
--host "$MONGO_DOMAIN" \
--eval "db.getSiblingDB('\$external').createUser({
user: '$NEW_DN',
roles: [
{role:'userAdminAnyDatabase', db:'admin'},
{role:'dbAdminAnyDatabase', db:'admin'},
{role:'readWriteAnyDatabase', db:'admin'}
]
})"
echo "[*] Removing old admin users..."
mongo --ssl \
--sslCAFile "$CA_DIR/ca.pem" \
--sslPEMKeyFile "$CLIENT_DIR/$NEW_NAME.pem" \
--host "$MONGO_DOMAIN" \
--eval 'db.getSiblingDB("$external").getUsers().forEach(function(u){ if(u.user.indexOf("CN=MongoAdmin")===0 && u.user!="'$NEW_DN'"){ db.dropUser(u.user); } });'
echo "[*] Restarting MongoDB..."
systemctl restart mongod
echo "[✔] Renewal complete. Active admin cert: $CLIENT_DIR/$NEW_NAME.pem"
EOF
chmod +x "$RENEW_SCRIPT"
# --- 11. Setup systemd monthly timer ---
cat > /etc/systemd/system/mongo-cert-renew.service <<EOF
[Unit]
Description=Renew MongoDB certs
[Service]
Type=oneshot
ExecStart=$RENEW_SCRIPT
EOF
cat > /etc/systemd/system/mongo-cert-renew.timer <<EOF
[Unit]
Description=Run MongoDB cert renew monthly
[Timer]
OnCalendar=monthly
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl enable mongo-cert-renew.timer
systemctl start mongo-cert-renew.timer
echo "[✔] Setup complete!"
echo "➡ Admin cert: $CLIENT_DIR/${ADMIN_CN}.pem"
echo "➡ App user: $APP_USER / $APP_PASS"
echo "➡ Admin certs rotate every 90 days; old cert users are automatically deleted."
echo "➡ App password users remain unaffected."
```
---
## 🔑 How This Works
- **Initial run**:
- Creates an admin cert (`CN=MongoAdmin`).
- Adds admin x.509 user in MongoDB.
- Creates app user (username/password).
- **On monthly renewal**:
- Let’s Encrypt server cert renewed.
- New admin cert generated (`CN=MongoAdmin-YYYYMMDD`).
- Added as admin in MongoDB.
- Old admin users matching `CN=MongoAdmin*` are dropped.
- MongoDB restarted.
- App user (`appuser`) is **untouched**.
---
✅ Result:
- **Only admins with fresh certs** can create new users.
- **Apps keep running** without interruption using username/password.
- **No risk of expired certs** because rotation + cleanup is automatic.
---
Do you want me to also add an **email/Slack alert** in the renewal script so you’re notified whenever a new admin cert is issued and the old one is dropped?